package com.weixin.project.Service;

import com.alibaba.fastjson.JSONArray;
import com.weixin.project.Common.Exception.AnswerFormatNotMatchException;
import com.weixin.project.Common.Enum.RatingScore;
import com.weixin.project.Config.Config;
import com.weixin.project.Dao.PracticeHistoryDao;
import com.weixin.project.Entity.Answer;
import com.weixin.project.Entity.PracticeHistory;
import com.weixin.project.Entity.Question;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@SuppressWarnings({"unchecked", "rawtypes"})
@Service
public class PracticeService {
    @Autowired
    PracticeHistoryDao practiceHistoryDao;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    QuestionService questionService;
    @Lazy
    @Autowired
    RecommendService recommendService;
    @Value("10")
    Integer pageSize;
    @Value("${logging.pattern.dateformat}")
    String dateFormat;

    //在service中所有出现过的redis namespace
    //entity
    private final static String PRACTICE_HISTORY = "practice";
    //list，用户在某一个题目上的做题记录
    private final static String USER_PRACTICE_OF_ONE_QUESTION_LIST = "user_practice_seq_of_one_question";
    //hash，用户在某一个章节上的做题位置，{"key::userId::chapter" : location}
    private final static String USER_LOCATION_OF_ONE_CHAPTER_HASH = "user_location_of_one_chapter";
    //list，用户的所有做题记录id
    private final static String USER_PRACTICE_HISTORY_ID_LIST = "user_practice_history_id_seq";
    //set，用户做过的所有题目id
    //TODO:使用set还是zset有待商榷
    public final static String USER_PRACTICED_QUESTIONS = "user_practice_question_id_set";

    /**
     * 寻找用户对某一问题的所有回答记录
     * @param questionId 问题Id
     * @param userId 用户自增Id
     * @return 用户对某一问题的所有回答记录
     */
    public List<PracticeHistory> findUserPracticesOfOneQuestion(Integer questionId, Integer userId){
        String listKey = USER_PRACTICE_OF_ONE_QUESTION_LIST + "::" + userId + "::" + questionId;
        if (!redisTemplate.hasKey(listKey)) {
            loadUserPracticesOfQuestion2Redis(userId, questionId);
        }
        redisTemplate.expire(listKey,180, TimeUnit.MINUTES);
        List<Integer> list = redisTemplate.boundListOps(listKey).range(0, -1);
        return list.stream().map(x -> findOneHistoryById(x)).
                sorted((p2, p1) -> p1.getCreateTime().compareTo(p2.getCreateTime())).collect(Collectors.toList());
    }

    public void practice(PracticeHistory practiceHistory){
        String key = USER_PRACTICE_OF_ONE_QUESTION_LIST + "::" + practiceHistory.getUserId()
                + "::" + practiceHistory.getQuestionId();
        if (!redisTemplate.hasKey(key))
            loadUserPracticesOfQuestion2Redis(practiceHistory.getUserId(), practiceHistory.getQuestionId());
        String setKey = USER_PRACTICED_QUESTIONS+ "::" + practiceHistory.getUserId();
        String listKey = USER_PRACTICE_HISTORY_ID_LIST + "::" + practiceHistory.getUserId();
        if (!(redisTemplate.hasKey(setKey)&&redisTemplate.hasKey(listKey)))
            loadUserPractices2Redis(practiceHistory.getUserId());
        try {
            judgeAnswer(practiceHistory, true);
        }catch (AnswerFormatNotMatchException e){
            return ;
        }
        redisTemplate.boundSetOps(setKey).add(practiceHistory.getQuestionId());
        redisTemplate.boundListOps(listKey).rightPush(practiceHistory.getId());
        redisTemplate.boundListOps(key).rightPush(practiceHistory.getId());
        cacheLastPracticeLoc(practiceHistory.getUserId(), questionService.findQuestionById(practiceHistory.getQuestionId()));
        recommendService.collectUserBehaviorForOnlineRec(practiceHistory.getUserId(),
                practiceHistory.getQuestionId(), practiceHistory.getIsRight()?RatingScore.CORRECT: RatingScore.ERROR);
//        kafkaService.sendUserBehavior(practiceHistory.getUserId(), practiceHistory.getQuestionId(), 0,
//                 new Date(), practiceHistory.getIsRight()?RatingScore.CORRECT: RatingScore.ERROR);
    }

//    @CachePut(value = PRACTICE_HISTORY, key = "#result.id", unless = "#result == null ")
    private PracticeHistory storePracticeHistory(PracticeHistory practiceHistory){
        return practiceHistoryDao.save(practiceHistory);
    }

    /**
     * 储存用户做题的位置
     * @param userId 用户自增Id
     * @param question question
     */
    private void cacheLastPracticeLoc(Integer userId, Question question){
        String hashKey = USER_LOCATION_OF_ONE_CHAPTER_HASH;
        String key = "key::" + "::" + userId + "::" + question.getChapterId();
        redisTemplate.boundHashOps(hashKey).put(key, question.getPosition());
    }

    /**
     * 寻找上一次做题位置
     * @param userId user的自增Id
     * @param chapterId 章节Id
     */
    public Integer findLastPracticeLoc(Integer userId, Integer chapterId){
        String hashKey = USER_LOCATION_OF_ONE_CHAPTER_HASH;
        String key = "key::" + "::" + userId + "_" + chapterId;
        if(!redisTemplate.boundHashOps(hashKey).hasKey(key)) return 0;
        return Integer.parseInt(redisTemplate.boundHashOps(key).get(key).toString());
    }

    /**
     * 寻找用户已经做过的题目的集合
     * @param userId user的自增Id
     * @return user已经做过的题目的题目id集合
     */
    public Set<Integer> findUserPracticedQuestionSet(Integer userId){
        Set<Integer> practiceSet = new HashSet<>();
        String key = USER_PRACTICED_QUESTIONS + "::" + userId;
        if(!redisTemplate.hasKey(key)){
            loadUserPractices2Redis(userId);
        }
        practiceSet.addAll(redisTemplate.boundSetOps(key).union(key));
        return practiceSet;
    }

    /**
     * 将用户做题的题目id和做题记录id加载到redis中
     * @param userId user的自增Id
     */
    public void loadUserPractices2Redis(Integer userId){
        String setKey = USER_PRACTICED_QUESTIONS + "::" + userId;
        String listKey = USER_PRACTICE_HISTORY_ID_LIST + "::" + userId;
        if (redisTemplate.hasKey(setKey)&&redisTemplate.hasKey(listKey)) return;
        List<PracticeHistory> histories = practiceHistoryDao.getPracticeHistoriesByUserIdOrderById(userId);
        List<Integer> questionIdSeq = histories.stream().map(x -> x.getQuestionId()).collect(Collectors.toList());
        List<Integer> historyIdSeq = histories.stream().map(x -> x.getId()).collect(Collectors.toList());
        System.out.println(historyIdSeq);
        if (!redisTemplate.hasKey(setKey)) {
            for (Integer questionId :
                    questionIdSeq) {
                redisTemplate.boundSetOps(setKey).add(questionId);
            }
        }
        if (!redisTemplate.hasKey(listKey)){
            for (Integer historyId :
                    historyIdSeq) {
                redisTemplate.boundListOps(listKey).rightPush(historyId);
            }
        }
    }

    /**
     * 将用户对某题目的做题记录加载到redis中
     * @param userId 用户id
     * @param questionId 问题id
     */
    public void loadUserPracticesOfQuestion2Redis(Integer userId, Integer questionId){
        String listKey = USER_PRACTICE_OF_ONE_QUESTION_LIST + "::" + userId + "::" + questionId;
        List<PracticeHistory> histories = practiceHistoryDao.getPracticeHistoriesByQuestionIdAndUserId(questionId, userId);
        histories.forEach(x -> redisTemplate.boundListOps(listKey).rightPush(x.getId()));
        redisTemplate.expire(listKey, 180, TimeUnit.MINUTES);
    }

    @Cacheable(value = PRACTICE_HISTORY, key = "#id", unless = "#result == null ")
    public PracticeHistory findOneHistoryById(Integer id){
        Optional<PracticeHistory> historyOptional = practiceHistoryDao.findById(id);
        return historyOptional.isPresent()?historyOptional.get():null;
    }

    /**
     * 返回用户所有做题记录
     * @param userId 用户自增Id
     * @param page 页号
     * @return 用户所有做题记录
     */
    public List<PracticeHistory> findUserPracticeHistories(Integer userId, Integer page){
        String key = USER_PRACTICE_HISTORY_ID_LIST + "::" + userId;
        if(!redisTemplate.hasKey(key)) loadUserPractices2Redis(userId);
        if(!redisTemplate.hasKey(key)) return new ArrayList<>();
        List<Integer> historyIdList = redisTemplate.boundListOps(key).range(0, -1);
        List<PracticeHistory> practiceHistories =
                historyIdList.stream().map(x -> findOneHistoryById(x)).collect(Collectors.toList());
        practiceHistories.stream().sorted((p2, p1) -> p1.getCreateTime().compareTo(p2.getCreateTime()));
        Integer startIndex = pageSize*page;
        Integer endIndex = Math.min(pageSize*(page+1), redisTemplate.boundListOps(key).size().intValue());
        return practiceHistories.subList(startIndex, endIndex);
    }

    /**
     * 计算用户做题记录总页数
     * @param userId 用户Id
     * @return 用户做题记录总页数
     */
    public Integer countUserPracticeHistoryTotalPage(Integer userId){
        String key = USER_PRACTICE_HISTORY_ID_LIST + "::" + userId;
        return (redisTemplate.boundListOps(key).size().intValue() - 1)/pageSize + 1;
    }

    /**
     * 计算用户做题量
     * @param userId 用户自增Id
     * @return 用户做题量
     */
    public Integer countExerciseAmountOfUser(Integer userId){
        String setKey = USER_PRACTICED_QUESTIONS + "::" + userId;
        if (!redisTemplate.hasKey(setKey)) loadUserPractices2Redis(userId);
        return redisTemplate.boundSetOps(setKey).size().intValue();
    }

    /**
     * 评判题目，返回一个flags，e.g. [true,true,false,true]
     * @param practiceHistory 做题记录
     */
    public void judgeAnswer(PracticeHistory practiceHistory, boolean isStored){
        List<Answer> answers = questionService.findAnswers(practiceHistory.getQuestionId());
        JSONArray myAnswers = JSONArray.parseArray(practiceHistory.getAnswer());
        if (myAnswers.size() != answers.size()){
            System.out.println("Error: answers length not matches! \n" +
                    "questionId:" + practiceHistory.getQuestionId());
            practiceHistory.setIsRight(false);
            throw new AnswerFormatNotMatchException("answer length not matches!");
        }
        JSONArray ret = new JSONArray();
        Integer typeId = practiceHistory.getTypeId();
        practiceHistory.setIsRight(true);
        for (int i = 0; i < answers.size(); i++) {
            //选择题和多选题
            if (typeId == 1 || typeId == 2) {
                if (!myAnswers.getBoolean(i).equals(answers.get(i).isRightAnswer())) {
                    ret.add(false);
                    practiceHistory.setIsRight(false);
                }
                else ret.add(true);
            }
            //判断题
            else if (typeId == 3){
                if (!myAnswers.getString(i).equals(answers.get(i).getContent())) {
                    ret.add(false);
                    practiceHistory.setIsRight(false);
                }
                else ret.add(true);
            }
            //填空题
            else if (typeId == 4){
                if (!Config.NLP_IS_ON){
                    if (!myAnswers.getString(i).equals(answers.get(i).getContent())) {
                        ret.add(false);
                        practiceHistory.setIsRight(false);
                    }
                    else ret.add(true);
                }
            }
        }
        practiceHistory.setFlags(ret.toString());
        if (isStored) storePracticeHistory(practiceHistory);
    }

    /**
     * 获取题目的正确答案对应的flags，e.g. [true,true,false,false]
     * @param questionId questionId
     * @param typeId typeId
     * @return 题目的正确答案对应的flags
     */
    public JSONArray getRightFlagsOfQuestion(Integer questionId, Integer typeId){
        List<Answer> answers = questionService.findAnswers(questionId);
        JSONArray ret = new JSONArray();
        switch (typeId){
            case 1:
            case 2:{
                if (answers.size()!=4) {
                    System.out.println("ERROR: The length of answers of" +
                            " Question::" + questionId + " in DB may have mistakes!");
                    return ret;
                }
                answers.forEach(x -> ret.add(x.isRightAnswer()?1:0));
                break;
            }
        }
        return ret;
    }

    /**
     * 查询用户近一个小时内练习过的题目
     * @return 题目id set
     */
    public Map<Integer, Set<Integer>> findPracticedQuestionsInOneHour() {
        List<PracticeHistory> practiceHistoryList = findPracticesInOneHour();
        return practiceHistoryList.stream()
                .collect(Collectors.groupingBy(PracticeHistory::getUserId,
                        Collectors.mapping(PracticeHistory::getQuestionId, Collectors.toSet())));
    }

    private List<PracticeHistory> findPracticesInOneHour(){
        Date now = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(now);
        cal.add(Calendar.HOUR, -1);
        Date oneHourBefore = cal.getTime();
        System.out.println("oneHourBefore:" + new SimpleDateFormat(dateFormat).format(oneHourBefore));
        return practiceHistoryDao.getPracticeHistoriesByCreateTimeAfter(oneHourBefore);
    }
}
